namespace Legend.Web.QueryString
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;

    /// <summary>
    /// Gets an sets the values of members decorated
    /// with the QueryStringParameterAttribute.
    /// </summary>
    public class AttributedParameterAccessor : IParameterAccessor
    {
        private object attributedQuery;
        private SerializableMemberInfoCollection members;

        /// <summary>
        /// Creates a new instance.
        /// </summary>
        /// <param name="attributedQuery">The object to access the attributed
        /// members of.</param>
        public AttributedParameterAccessor(object attributedQuery)
        {
            Require.ThatArgument(attributedQuery).Named("attributedQuery").IsNotNull();

            this.attributedQuery = attributedQuery;
            this.members =
                new DataContractMemberAccessor(attributedQuery.GetType())
                    .GetSerializableMembers();
        }

        /// <summary>
        /// Gets the names and values for all the parameters and fields
        /// attributed on the object.
        /// </summary>
        public IDictionary<string, string> GetAllParametersAndValues()
        {
            var result = new Dictionary<string, string>();

            foreach (var member in this.members)
            {
                result.Add(member.Name, GetStringValue(member));
            }

            return result;
        }

        /// <summary>
        /// Apply the specified parameter value.
        /// </summary>
        /// <param name="parameterName">The name of the parameter to apply the value to.</param>
        /// <param name="value">The value to apply to the parameter.</param>
        public void ApplyParameterValue(string parameterName, string value)
        {
            this.EnsureThatMemberExists(parameterName);

            SerializableMemberInfo member = this.members[parameterName];
            object convertedValue = ConvertValueFromString(value, member.Converter);
 
            member.SetValue(this.attributedQuery, convertedValue);
        }

        private string GetStringValue(SerializableMemberInfo member)
        {
            return member.Converter.ConvertToInvariantString(member.GetValue(this.attributedQuery));
        }

        private static object ConvertValueFromString(string value, TypeConverter converter)
        {
            object convertedValue;

            try
            {
                convertedValue = converter.ConvertFromInvariantString(value);
            }
            catch (Exception)
            {
                throw new ArgumentOutOfRangeException("value", "The specified value '{0}' could not be converted.".FormatInvariant(value));
            }

            return convertedValue;
        }

        private void EnsureThatMemberExists(string parameterName)
        {
            if (!this.members.Contains(parameterName))
            {
                throw new ArgumentOutOfRangeException("parameterName", "The specified parameter '{0}' was not found on the attributed object.".FormatInvariant(parameterName));
            }
        }
    }
}